package lock;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import redis.clients.jedis.Jedis;

/**
 * Redis Lock：redis锁（注意：必须在事务外使用，否则解锁-事务提交之间，可能被其他线程lock，导致脏读）
 */
public class RedisLock {

	private static Logger logger = LoggerFactory.getLogger(RedisLock.class);

	private String[] keys;
	private int expireSeconds; // 过期时间
	private int retrySeconds; // 重试时间
	private long timeValue = 0; // lock设置的时间戳：0 失败；>0 时间戳

	private String lockPrefix;
	private List<String> lockKeys; // 已加锁的key列表

	public RedisLock(int expireSeconds,int retrySeconds,String lockPrefix,String... keys) {
		this.keys = keys;
		this.expireSeconds = expireSeconds;
		this.retrySeconds = retrySeconds;
		this.lockPrefix = lockPrefix;
		this.lockKeys = new LinkedList<String>();
	}

	public RedisLock(String prefix, Object... objs) {
		this.keys = new String[objs.length];
		for (int i = 0; i < objs.length; i++) {
			keys[i] = prefix + objs[i];
		}
	}

	public int getExpireSeconds() {
		return expireSeconds;
	}

	public void setExpireSeconds(int expireSeconds) {
		this.expireSeconds = expireSeconds;
	}

	public int getRetrySeconds() {
		return retrySeconds;
	}

	public void setRetrySeconds(int retrySeconds) {
		this.retrySeconds = retrySeconds;
	}

	public String[] getKeys() {
		return keys;
	}

	public long getTimeValue() {
		return timeValue;
	}

	public List<String> getLockKeys() {
		return lockKeys;
	}

	/**
	 * 排序自旋锁：setnx && expire
	 */
	public boolean acquire() {
		Jedis jedis = Redis.getInstance().getJedis();
		try {
			Arrays.sort(keys);
			lockKeys.clear();
			long now = System.currentTimeMillis();
			// 避免死锁：value = 当前时间 + 过期时间 + 1毫秒
			this.timeValue = now + expireSeconds * 1000 + 1;
			while (true) {
				for (String key : keys) {
					String lockKey = lockPrefix + key;
					if (lockKeys.contains(lockKey))
						continue;

					Long ret = jedis.setnx(lockKey, String.valueOf(timeValue));
					if (ret == 1) {
						// 使用expire，如果代码运行至此，redis连接崩溃，该key没有设置超时时间 => 死锁
						jedis.expire(lockKey, expireSeconds);
						lockKeys.add(lockKey);
					}
					// 解决死锁：判断key对应的value是否超时
					else {
						String currentValueStr = jedis.get(lockKey);
						if (currentValueStr != null && System.currentTimeMillis() > Long.valueOf(currentValueStr)) {
							// 并发竞争：getset取出旧值再判断一次，避免被其他线程set
							String oldValueStr = jedis.getSet(lockKey, String.valueOf(timeValue));
							if (oldValueStr == null || oldValueStr.equals(currentValueStr)) {
								lockKeys.add(lockKey);
								continue;
							}
						}
						break;
					}
				}
				// 全部锁住
				if (lockKeys.size() == keys.length) {
					return true;
				}
				// 超时未成功：释放加的锁
				else if (System.currentTimeMillis() - now > retrySeconds * 1000) {
					if (lockKeys.size() > 0) {
						jedis.del(lockKeys.toArray(new String[lockKeys.size()]));
						lockKeys.clear();
					}
					return false;
				}

				// TODO 当前线程将持有jedis一段时间（retrySeconds），如果并发数过大，可能会导致jedis连接数不足
				Thread.sleep(200);
			}
		} catch (Exception e) {
			logger.warn("获取redis lock异常", e);
			if (lockKeys.size() > 0) {
				jedis.del(lockKeys.toArray(new String[lockKeys.size()]));
				lockKeys.clear();
			}
			return false;
		} finally {
			Redis.getInstance().close(jedis);
		}
	}

	/**
	 * 解锁：删除 key（从redis线程池未获取到jedis => 解锁失败）
	 */
	public void release() {
		Jedis jedis = Redis.getInstance().getJedis();
		try {
			for (String lockKey : lockKeys) {
				String valueStr = jedis.get(lockKey);
				// timeValue不等：可能发生当前线程lock后，业务操作比较耗时，导致超时后被其他线程lock
				if (valueStr != null && timeValue == Long.parseLong(valueStr))
					jedis.del(lockKey);
			}
			lockKeys.clear();
		} catch (Exception e) {
			logger.warn("释放redis lock异常", e);
		} finally {
			this.timeValue = 0;
			Redis.getInstance().close(jedis);
		}
	}

//	/**
//	 * 匿名类包装：无返回值
//	 * 
//	 * @param runnable 需要锁住的运行代码
//	 * @return true 获锁成功；false 获锁失败
//	 */
//	public boolean wrap(Runnable runnable) {
//		if (acquire()) {
//			try {
//				runnable.run();
//			} catch (Exception e) {
//				logger.error(e.getMessage(), e);
//				if (e instanceof BusinessException)
//					throw (BusinessException) e;
//				else
//					throw new BusinessException(ErrorCode.SERVER_ERROR, e);
//			} finally {
//				release();
//			}
//			return true;
//		}
//		else
//			return false;
//	}

//	/**
//	 * 匿名类包装：带返回值
//	 * 
//	 * @param callable 需要锁住的运行代码
//	 */
//	public <V> V wrap(Callable<V> callable) {
//		if (acquire()) {
//			try {
//				return callable.call();
//			} catch (Exception e) {
//				logger.error(e.getMessage(), e);
//				if (e instanceof BusinessException)
//					throw (BusinessException) e;
//				else
//					throw new BusinessException(ErrorCode.SERVER_ERROR, e);
//			} finally {
//				release();
//			}
//		}
//		else
//			return null;
//	}

}
